# -*- coding: utf-8 -*-
"""
智能彩票爬虫调度器
根据API限制和彩种需求优化数据抓取策略
"""

import json
import time
from datetime import datetime, date
from typing import Dict, List, Optional, Tuple
from loguru import logger

from .crawler_manager import CrawlerManager
from .database import db_manager
from .config.settings import LOTTERY_TYPES


class SmartScheduler:
    """智能调度器"""
    
    def __init__(self):
        self.crawler_manager = CrawlerManager()
        
        # 每日抓取配置（根据极速数据API限制优化）
        # API每次最多返回20条，大乐透和双色球每日14次调用，其他彩种12次调用
        self.daily_config = {
            'ssq': {'count': 280, 'priority': 1, 'api_calls': 14},  # 双色球：280条(14*20)，高优先级
            'dlt': {'count': 280, 'priority': 1, 'api_calls': 14},  # 大乐透：280条(14*20)，高优先级
            'fc3d': {'count': 240, 'priority': 2, 'api_calls': 12}, # 福彩3D：240条(12*20)，中优先级
            'pl3': {'count': 240, 'priority': 2, 'api_calls': 12},  # 排列3：240条(12*20)，中优先级
            'pl5': {'count': 240, 'priority': 2, 'api_calls': 12},  # 排列5：240条(12*20)，中优先级
            'kl8': {'count': 240, 'priority': 3, 'api_calls': 12},  # 快乐8：240条(12*20)，低优先级
            'qlc': {'count': 240, 'priority': 4, 'api_calls': 12},  # 七乐彩：240条(12*20)，最低优先级
            'qxc': {'count': 240, 'priority': 4, 'api_calls': 12},  # 七星彩：240条(12*20)，最低优先级
        }
        
        # API优化配置：根据极速数据API实际限制
        self.api_optimization = {
            'batch_size': 20,  # 每次API请求20条数据（极速数据API最大值）
            'enable_batching': True,  # 启用批量请求
            'total_daily_calls': 98  # 总计每日调用次数：14+14+12*6=98次
        }
        
        # API限制配置
        self.api_limits = {
            'daily_limit': 100,  # 每日免费次数
            'used_today': 0,     # 今日已使用次数
            'last_reset_date': None  # 上次重置日期
        }
        
        # 状态文件路径
        self.state_file = '/Users/ly/Desktop/lottery/scripts/crawler/data/scheduler_state.json'
        
        # 加载状态
        self.load_state()
    
    def load_state(self):
        """加载调度器状态"""
        try:
            with open(self.state_file, 'r', encoding='utf-8') as f:
                state = json.load(f)
                self.api_limits.update(state.get('api_limits', {}))
                
                # 检查是否需要重置每日计数
                today = date.today().isoformat()
                if self.api_limits.get('last_reset_date') != today:
                    self.api_limits['used_today'] = 0
                    self.api_limits['last_reset_date'] = today
                    logger.info("每日API使用次数已重置")
                    
        except FileNotFoundError:
            logger.info("状态文件不存在，使用默认配置")
            self.api_limits['last_reset_date'] = date.today().isoformat()
        except Exception as e:
            logger.error(f"加载状态文件失败: {e}")
    
    def save_state(self):
        """保存调度器状态"""
        try:
            import os
            os.makedirs(os.path.dirname(self.state_file), exist_ok=True)
            
            state = {
                'api_limits': self.api_limits,
                'last_update': datetime.now().isoformat()
            }
            
            with open(self.state_file, 'w', encoding='utf-8') as f:
                json.dump(state, f, ensure_ascii=False, indent=2)
                
        except Exception as e:
            logger.error(f"保存状态文件失败: {e}")
    
    def get_remaining_api_calls(self) -> int:
        """获取剩余API调用次数"""
        return max(0, self.api_limits['daily_limit'] - self.api_limits['used_today'])
    
    def get_total_daily_api_calls(self) -> int:
        """计算每日总API调用次数"""
        total_calls = 0
        for lottery_type, config in self.daily_config.items():
            total_calls += config.get('api_calls', 1)
        return total_calls
    
    def validate_api_efficiency(self) -> dict:
        """验证API调用效率"""
        total_data_needed = sum(config['count'] for config in self.daily_config.values())
        total_api_calls = self.get_total_daily_api_calls()
        efficiency = total_data_needed / total_api_calls if total_api_calls > 0 else 0
        
        return {
            'total_data_needed': total_data_needed,
            'total_api_calls': total_api_calls,
            'efficiency': efficiency,
            'data_per_call': efficiency,
            'within_limit': total_api_calls <= self.api_limits['daily_limit']
        }
    
    def calculate_next_periods(self, lottery_type: str, current_latest: str, count: int) -> Tuple[str, int]:
        """计算下次应该抓取的起始期号和数量
        
        Args:
            lottery_type: 彩种类型
            current_latest: 当前最新期号
            count: 需要抓取的数量
            
        Returns:
            (起始期号, 实际抓取数量)
        """
        if not current_latest:
            # 如果没有历史数据，从最新开始抓取
            return None, count
        
        try:
            # 解析期号格式
            if lottery_type in ['ssq', 'fc3d', 'qlc']:  # 年份+期号格式 (如2025085)
                year = int(current_latest[:4])
                period = int(current_latest[4:])
                
                # 计算起始期号（向前推count期）
                start_period = max(1, period - count)
                start_period_str = f"{year}{start_period:03d}"
                
                return start_period_str, count
                
            elif lottery_type in ['dlt', 'pl3', 'pl5', 'qxc']:  # 年份后两位+期号格式 (如25085)
                year_suffix = int(current_latest[:2])
                period = int(current_latest[2:])
                
                # 计算起始期号（向前推count期）
                start_period = max(1, period - count)
                start_period_str = f"{year_suffix}{start_period:03d}"
                
                return start_period_str, count
                
            elif lottery_type == 'kl8':  # 特殊格式
                # 快乐8的期号格式可能不同，需要特殊处理
                period = int(current_latest)
                start_period = max(1, period - count)
                return str(start_period), count
                
        except Exception as e:
            logger.error(f"计算{lottery_type}下次期号失败: {e}")
            
        return None, count
    
    def get_lottery_status(self) -> Dict:
        """获取所有彩种的当前状态"""
        status = {}
        
        for lottery_type in self.daily_config.keys():
            try:
                latest_period = db_manager.get_latest_period(lottery_type)
                query = "SELECT COUNT(*) as count FROM lottery_results WHERE lottery_type = %s"
                result = db_manager.execute_query(query, (lottery_type,))
                count = result[0]['count'] if result else 0
                
                status[lottery_type] = {
                    'latest_period': latest_period,
                    'total_records': count,
                    'daily_target': self.daily_config[lottery_type]['count'],
                    'priority': self.daily_config[lottery_type]['priority']
                }
                
            except Exception as e:
                logger.error(f"获取{lottery_type}状态失败: {e}")
                status[lottery_type] = {
                    'latest_period': None,
                    'total_records': 0,
                    'daily_target': self.daily_config[lottery_type]['count'],
                    'priority': self.daily_config[lottery_type]['priority']
                }
        
        return status
    
    def plan_daily_crawl(self) -> List[Dict]:
        """制定每日抓取计划"""
        remaining_calls = self.get_remaining_api_calls()
        
        if remaining_calls <= 0:
            logger.warning("今日API调用次数已用完")
            return []
        
        # 获取当前状态
        lottery_status = self.get_lottery_status()
        
        # 生成抓取计划
        crawl_plan = []
        total_calls_needed = 0
        
        # 按优先级排序
        sorted_lotteries = sorted(
            lottery_status.items(),
            key=lambda x: x[1]['priority']
        )
        
        for lottery_type, status in sorted_lotteries:
            config = self.daily_config[lottery_type]
            target_count = config['count']
            
            # 计算下次抓取的起始期号
            start_period, actual_count = self.calculate_next_periods(
                lottery_type,
                status['latest_period'],
                target_count
            )
            
            # 估算API调用次数（每次调用最多获取50条数据）
            api_calls_needed = max(1, (actual_count + 49) // 50)
            
            if total_calls_needed + api_calls_needed <= remaining_calls:
                plan_item = {
                    'lottery_type': lottery_type,
                    'mode': 'history' if start_period else 'latest',
                    'count': actual_count,
                    'start_period': start_period,
                    'priority': config['priority'],
                    'api_calls_estimated': api_calls_needed,
                    'current_latest': status['latest_period']
                }
                
                crawl_plan.append(plan_item)
                total_calls_needed += api_calls_needed
            else:
                logger.warning(f"API调用次数不足，跳过{lottery_type}（需要{api_calls_needed}次调用）")
        
        logger.info(f"制定抓取计划完成，共{len(crawl_plan)}个任务，预计使用{total_calls_needed}次API调用")
        return crawl_plan
    
    def execute_crawl_plan(self, plan: List[Dict]) -> List[Dict]:
        """执行抓取计划"""
        results = []
        
        for item in plan:
            try:
                logger.info(f"开始执行: {item['lottery_type']} - {item['mode']}模式，目标{item['count']}条")
                
                # 执行抓取
                if item['mode'] == 'latest':
                    result = self.crawler_manager.crawl_single_lottery(
                        item['lottery_type'],
                        mode='latest',
                        count=item['count']
                    )
                else:
                    # 历史数据抓取需要特殊处理
                    result = self.crawl_history_from_period(
                        item['lottery_type'],
                        item['start_period'],
                        item['count']
                    )
                
                # 更新API使用次数
                self.api_limits['used_today'] += item['api_calls_estimated']
                
                results.append(result)
                
                # 保存状态
                self.save_state()
                
                logger.info(f"{item['lottery_type']}抓取完成，保存{result.get('saved_count', 0)}条")
                
                # 短暂延迟，避免API限制
                time.sleep(1)
                
            except Exception as e:
                logger.error(f"执行{item['lottery_type']}抓取失败: {e}")
                results.append({
                    'lottery_type': item['lottery_type'],
                    'success': False,
                    'error': str(e),
                    'saved_count': 0
                })
        
        return results
    
    def crawl_history_from_period(self, lottery_type: str, start_period: str, count: int) -> Dict:
        """从指定期号开始抓取历史数据"""
        try:
            spider = self.crawler_manager.get_spider(lottery_type)
            
            # 使用最新数据模式，但指定数量
            saved_count = spider.run(mode='latest', count=count)
            
            return {
                'lottery_type': lottery_type,
                'mode': 'history_from_period',
                'start_period': start_period,
                'saved_count': saved_count,
                'success': saved_count > 0,
                'timestamp': datetime.now().isoformat()
            }
            
        except Exception as e:
            logger.error(f"从期号{start_period}抓取{lottery_type}历史数据失败: {e}")
            return {
                'lottery_type': lottery_type,
                'mode': 'history_from_period',
                'start_period': start_period,
                'saved_count': 0,
                'success': False,
                'error': str(e),
                'timestamp': datetime.now().isoformat()
            }
    
    def run_daily_crawl(self) -> Dict:
        """执行每日智能抓取"""
        logger.info("开始执行每日智能抓取任务")
        
        start_time = time.time()
        
        # 制定抓取计划
        plan = self.plan_daily_crawl()
        
        if not plan:
            return {
                'success': False,
                'message': '今日API调用次数已用完或无可执行计划',
                'results': [],
                'api_usage': self.api_limits
            }
        
        # 执行抓取计划
        results = self.execute_crawl_plan(plan)
        
        # 统计结果
        success_count = sum(1 for r in results if r.get('success', False))
        total_saved = sum(r.get('saved_count', 0) for r in results)
        
        end_time = time.time()
        duration = end_time - start_time
        
        summary = {
            'success': success_count > 0,
            'total_tasks': len(plan),
            'success_tasks': success_count,
            'total_saved_records': total_saved,
            'duration': duration,
            'api_usage': {
                'used_today': self.api_limits['used_today'],
                'remaining': self.get_remaining_api_calls(),
                'daily_limit': self.api_limits['daily_limit']
            },
            'results': results,
            'timestamp': datetime.now().isoformat()
        }
        
        logger.info(f"每日智能抓取完成：{success_count}/{len(plan)}个任务成功，共保存{total_saved}条记录，耗时{duration:.2f}秒")
        
        return summary
    
    def get_status_report(self) -> Dict:
        """获取详细状态报告"""
        lottery_status = self.get_lottery_status()
        
        return {
            'api_usage': {
                'used_today': self.api_limits['used_today'],
                'remaining': self.get_remaining_api_calls(),
                'daily_limit': self.api_limits['daily_limit'],
                'last_reset_date': self.api_limits['last_reset_date']
            },
            'lottery_status': lottery_status,
            'daily_config': self.daily_config,
            'next_plan_preview': self.plan_daily_crawl()
        }